// Package errors provides enhanced error handling and recovery mechanisms
package errors

import (
	"context"
	"fmt"
	"runtime"
	"strings"
	"sync"
	"time"

	"github.com/smart-snapshotter/internal/logging"
)

// ErrorSeverity defines error severity levels
type ErrorSeverity int

const (
	SeverityLow ErrorSeverity = iota
	SeverityMedium
	SeverityHigh
	SeverityCritical
)

// ErrorCategory defines error categories
type ErrorCategory int

const (
	CategorySystem ErrorCategory = iota
	CategoryStorage
	CategoryNetwork
	CategoryDeduplication
	CategoryCache
	CategoryConfiguration
	CategoryValidation
)

// ErrorContext provides additional error context
type ErrorContext struct {
	Severity     ErrorSeverity
	Category     ErrorCategory
	Component    string
	Operation    string
	Retryable    bool
	Recoverable  bool
	Timestamp    time.Time
	StackTrace   string
	Metadata     map[string]interface{}
}

// EnhancedError represents an enhanced error with context
type EnhancedError struct {
	Message string
	Cause   error
	Context *ErrorContext
}

// Error implements the error interface
func (e *EnhancedError) Error() string {
	if e.Cause != nil {
		return fmt.Sprintf("%s: %v", e.Message, e.Cause)
	}
	return e.Message
}

// Unwrap returns the wrapped error
func (e *EnhancedError) Unwrap() error {
	return e.Cause
}

// IsRetryable returns whether the error is retryable
func (e *EnhancedError) IsRetryable() bool {
	return e.Context != nil && e.Context.Retryable
}

// IsRecoverable returns whether the error is recoverable
func (e *EnhancedError) IsRecoverable() bool {
	return e.Context != nil && e.Context.Recoverable
}

// GetSeverity returns the error severity
func (e *EnhancedError) GetSeverity() ErrorSeverity {
	if e.Context != nil {
		return e.Context.Severity
	}
	return SeverityMedium
}

// GetCategory returns the error category
func (e *EnhancedError) GetCategory() ErrorCategory {
	if e.Context != nil {
		return e.Context.Category
	}
	return CategorySystem
}

// ErrorBuilder provides a fluent interface for error creation
type ErrorBuilder struct {
	message string
	cause   error
	context *ErrorContext
}

// NewError creates a new error builder
func NewError(message string) *ErrorBuilder {
	return &ErrorBuilder{
		message: message,
		context: &ErrorContext{
			Timestamp: time.Now(),
			Metadata:  make(map[string]interface{}),
		},
	}
}

// WrapError wraps an existing error
func WrapError(cause error, message string) *ErrorBuilder {
	return &ErrorBuilder{
		message: message,
		cause:   cause,
		context: &ErrorContext{
			Timestamp: time.Now(),
			Metadata:  make(map[string]interface{}),
		},
	}
}

// WithSeverity sets the error severity
func (b *ErrorBuilder) WithSeverity(severity ErrorSeverity) *ErrorBuilder {
	b.context.Severity = severity
	return b
}

// WithCategory sets the error category
func (b *ErrorBuilder) WithCategory(category ErrorCategory) *ErrorBuilder {
	b.context.Category = category
	return b
}

// WithComponent sets the component
func (b *ErrorBuilder) WithComponent(component string) *ErrorBuilder {
	b.context.Component = component
	return b
}

// WithOperation sets the operation
func (b *ErrorBuilder) WithOperation(operation string) *ErrorBuilder {
	b.context.Operation = operation
	return b
}

// WithRetryable sets whether the error is retryable
func (b *ErrorBuilder) WithRetryable(retryable bool) *ErrorBuilder {
	b.context.Retryable = retryable
	return b
}

// WithRecoverable sets whether the error is recoverable
func (b *ErrorBuilder) WithRecoverable(recoverable bool) *ErrorBuilder {
	b.context.Recoverable = recoverable
	return b
}

// WithMetadata adds metadata
func (b *ErrorBuilder) WithMetadata(key string, value interface{}) *ErrorBuilder {
	b.context.Metadata[key] = value
	return b
}

// WithStackTrace adds stack trace
func (b *ErrorBuilder) WithStackTrace() *ErrorBuilder {
	const skip = 3 // Skip this function and the builder methods
	pc := make([]uintptr, 32)
	n := runtime.Callers(skip, pc)
	frames := runtime.CallersFrames(pc[:n])

	var stack strings.Builder
	for {
		frame, more := frames.Next()
		if !strings.Contains(frame.File, "smart-snapshotter") {
			continue
		}
		stack.WriteString(fmt.Sprintf("%s:%d %s\n", frame.File, frame.Line, frame.Function))
		if !more {
			break
		}
	}
	b.context.StackTrace = stack.String()
	return b
}

// Build builds the enhanced error
func (b *ErrorBuilder) Build() *EnhancedError {
	return &EnhancedError{
		Message: b.message,
		Cause:   b.cause,
		Context: b.context,
	}
}

// RecoveryStrategy defines error recovery strategies
type RecoveryStrategy interface {
	CanRecover(err error) bool
	Recover(ctx context.Context, err error) error
	GetName() string
}

// BaseRecoveryStrategy provides common recovery functionality
type BaseRecoveryStrategy struct {
	name      string
	maxRetries int
	retryDelay time.Duration
}

// GetName returns the strategy name
func (s *BaseRecoveryStrategy) GetName() string {
	return s.name
}

// RetryableRecoveryStrategy handles retryable errors
type RetryableRecoveryStrategy struct {
	BaseRecoveryStrategy
}

// NewRetryableRecoveryStrategy creates a new retryable recovery strategy
func NewRetryableRecoveryStrategy(name string, maxRetries int, retryDelay time.Duration) *RetryableRecoveryStrategy {
	return &RetryableRecoveryStrategy{
		BaseRecoveryStrategy: BaseRecoveryStrategy{
			name:       name,
			maxRetries: maxRetries,
			retryDelay: retryDelay,
		},
	}
}

// CanRecover returns whether the strategy can recover from the error
func (s *RetryableRecoveryStrategy) CanRecover(err error) bool {
	enhancedErr, ok := err.(*EnhancedError)
	if !ok {
		return false
	}
	return enhancedErr.IsRetryable()
}

// Recover attempts to recover from the error
func (s *RetryableRecoveryStrategy) Recover(ctx context.Context, err error) error {
	logging.WithContext(ctx).WithFields(
		logging.StringField("strategy", s.name),
		logging.StringField("error", err.Error()),
	).Info("Attempting recovery with retryable strategy")

	var lastErr error
	for i := 0; i < s.maxRetries; i++ {
		select {
		case <-ctx.Done():
			return fmt.Errorf("recovery cancelled: %w", ctx.Err())
		case <-time.After(s.retryDelay):
			// Retry the operation
			logging.WithContext(ctx).WithFields(
				logging.StringField("strategy", s.name),
				logging.IntField("attempt", i+1),
				logging.IntField("max_retries", s.maxRetries),
			).Info("Retry attempt")

			// For now, just return success to simulate recovery
			// In real implementation, this would retry the actual operation
			return nil
		}
	}

	return fmt.Errorf("recovery failed after %d retries: %w", s.maxRetries, lastErr)
}

// CircuitBreakerRecoveryStrategy handles circuit breaker pattern
type CircuitBreakerRecoveryStrategy struct {
	BaseRecoveryStrategy
	failureThreshold int
	resetTimeout     time.Duration
	failureCount     int
	lastFailureTime  time.Time
	state            string // "closed", "open", "half-open"
	mu               sync.Mutex
}

// NewCircuitBreakerRecoveryStrategy creates a new circuit breaker strategy
func NewCircuitBreakerRecoveryStrategy(name string, failureThreshold int, resetTimeout time.Duration) *CircuitBreakerRecoveryStrategy {
	return &CircuitBreakerRecoveryStrategy{
		BaseRecoveryStrategy: BaseRecoveryStrategy{name: name},
		failureThreshold:     failureThreshold,
		resetTimeout:         resetTimeout,
		state:                "closed",
	}
}

// CanRecover returns whether the strategy can recover from the error
func (s *CircuitBreakerRecoveryStrategy) CanRecover(err error) bool {
	s.mu.Lock()
	defer s.mu.Unlock()

	if s.state == "open" {
		if time.Since(s.lastFailureTime) > s.resetTimeout {
			s.state = "half-open"
			return true
		}
		return false
	}

	return s.state == "closed" || s.state == "half-open"
}

// Recover attempts to recover from the error
func (s *CircuitBreakerRecoveryStrategy) Recover(ctx context.Context, err error) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	logging.WithContext(ctx).WithFields(
		logging.StringField("strategy", s.name),
		logging.StringField("state", s.state),
		logging.StringField("error", err.Error()),
	).Info("Attempting recovery with circuit breaker strategy")

	if s.state == "open" {
		return fmt.Errorf("circuit breaker is open")
	}

	// Simulate operation attempt
	// In real implementation, this would attempt the actual operation
	success := true // Simulate success for now

	if success {
		if s.state == "half-open" {
			s.state = "closed"
			s.failureCount = 0
		}
		return nil
	} else {
		s.failureCount++
		s.lastFailureTime = time.Now()

		if s.failureCount >= s.failureThreshold {
			s.state = "open"
			return fmt.Errorf("circuit breaker opened after %d failures", s.failureCount)
		}

		return fmt.Errorf("operation failed, failure count: %d", s.failureCount)
	}
}

// ErrorManager manages error handling and recovery
type ErrorManager struct {
	strategies []RecoveryStrategy
	mu         sync.RWMutex
}

// NewErrorManager creates a new error manager
func NewErrorManager() *ErrorManager {
	return &ErrorManager{
		strategies: make([]RecoveryStrategy, 0),
	}
}

// AddStrategy adds a recovery strategy
func (em *ErrorManager) AddStrategy(strategy RecoveryStrategy) {
	em.mu.Lock()
	defer em.mu.Unlock()
	em.strategies = append(em.strategies, strategy)
}

// HandleError handles an error with appropriate recovery strategy
func (em *ErrorManager) HandleError(ctx context.Context, err error) error {
	if err == nil {
		return nil
	}

	logging.WithContext(ctx).WithFields(
		logging.StringField("error", err.Error()),
		logging.StringField("error_type", fmt.Sprintf("%T", err)),
	).Error("Handling error")

	em.mu.RLock()
	strategies := make([]RecoveryStrategy, len(em.strategies))
	copy(strategies, em.strategies)
	em.mu.RUnlock()

	// Try each strategy in order
	for _, strategy := range strategies {
		if strategy.CanRecover(err) {
			logging.WithContext(ctx).WithFields(
				logging.StringField("strategy", strategy.GetName()),
			).Info("Attempting recovery")

			recoveryErr := strategy.Recover(ctx, err)
			if recoveryErr == nil {
				logging.WithContext(ctx).Info("Recovery successful")
				return nil
			}

			logging.WithContext(ctx).WithFields(
				logging.StringField("strategy", strategy.GetName()),
				logging.StringField("recovery_error", recoveryErr.Error()),
			).Warn("Recovery failed")
		}
	}

	// No strategy could recover from the error
	logging.WithContext(ctx).Error("No recovery strategy available for error")
	return err
}

// Global error manager instance
var (
	globalErrorManager *ErrorManager
	errorManagerOnce   sync.Once
)

// InitGlobalErrorManager initializes the global error manager
func InitGlobalErrorManager() {
	errorManagerOnce.Do(func() {
		globalErrorManager = NewErrorManager()

		// Add default strategies
		globalErrorManager.AddStrategy(NewRetryableRecoveryStrategy("default_retry", 3, 1*time.Second))
		globalErrorManager.AddStrategy(NewCircuitBreakerRecoveryStrategy("default_circuit_breaker", 5, 30*time.Second))
	})
}

// GetErrorManager returns the global error manager
func GetErrorManager() *ErrorManager {
	if globalErrorManager == nil {
		InitGlobalErrorManager()
	}
	return globalErrorManager
}

// HandleError handles an error using the global error manager
func HandleError(ctx context.Context, err error) error {
	return GetErrorManager().HandleError(ctx, err)
}

// Common error creation functions

// NewStorageError creates a storage-related error
func NewStorageError(message string, cause error) *EnhancedError {
	return NewError(message).
		WithCause(cause).
		WithCategory(CategoryStorage).
		WithSeverity(SeverityHigh).
		WithRetryable(true).
		WithRecoverable(true).
		WithStackTrace().
		Build()
}

// NewNetworkError creates a network-related error
func NewNetworkError(message string, cause error) *EnhancedError {
	return NewError(message).
		WithCause(cause).
		WithCategory(CategoryNetwork).
		WithSeverity(SeverityMedium).
		WithRetryable(true).
		WithRecoverable(true).
		WithStackTrace().
		Build()
}

// NewConfigurationError creates a configuration-related error
func NewConfigurationError(message string, cause error) *EnhancedError {
	return NewError(message).
		WithCause(cause).
		WithCategory(CategoryConfiguration).
		WithSeverity(SeverityHigh).
		WithRetryable(false).
		WithRecoverable(false).
		WithStackTrace().
		Build()
}

// NewValidationError creates a validation error
func NewValidationError(message string, cause error) *EnhancedError {
	return NewError(message).
		WithCause(cause).
		WithCategory(CategoryValidation).
		WithSeverity(SeverityLow).
		WithRetryable(false).
		WithRecoverable(false).
		WithStackTrace().
		Build()
}

// Helper method for ErrorBuilder
func (b *ErrorBuilder) WithCause(cause error) *ErrorBuilder {
	b.cause = cause
	return b
}